Spring MVC 之 <context:component-scan/>

版权声明:本文为博主原创文章,转载请注明出处,谢谢!

版权声明:本文为博主原创文章,转载请注明出处:http://blog.jerkybible.com/2015/05/19/Spring MVC 之 contextcomponent-scan/

访问原文「Spring MVC 之 <context:component-scan/>

一、ContextLoader
通过阅读SpringMVC的源码可知,SpringMVC的初始化在ContextLoaderListener类中的contextInitialized方法,跟一下代码发现实际上是在ContextLoader中的initWebApplicationContext方法中进行初始化。初始化的代码如下,首先会创建一个WebApplicationContext对象,然后加载父类,接着使用configureAndRefreshWebApplicationContext初始化这个WebApplicactionContext对象,是在configureAndRefreshWebApplicationContext方法中进行了配置文件的加载和组件的扫描。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -&gt; provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -&gt;
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}

展开createWebApplicationContext方法。该方法初始化根WebApplicationContext,这个WebApplicationContext可以使默认的context也尅进行定制。

1
2
3
4
5
6
7
8
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class&lt;?&gt; contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(&quot;Custom context class [&quot; + contextClass.getName() +
&quot;] is not of type [&quot; + ConfigurableWebApplicationContext.class.getName() + &quot;]&quot;);
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

展开determineContextClass方法。这个方法比较简单,首先查看有没有配置CONTEXT_CLASS_PARAM这个属性,有的话使用这个属性配置的类进行加载,否则使用默认的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected Class&lt;?&gt; determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
&quot;Failed to load custom context class [&quot; + contextClassName + &quot;]&quot;, ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
&quot;Failed to load default context class [&quot; + contextClassName + &quot;]&quot;, ex);
}
}
}

再看configureAndRefreshWebApplicationContext方法。configureAndRefreshWebApplicationContext最后的refresh方法已经在前面的Spring 使用简单Demo进行源码调试(一)系列文章里进行了简单的分析调试。

二、 <context:component-scan/>
我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比较经常用XML配置,控制层依赖的service比较经常用注解等(在部署时比较不会改变的),我们经常比较常用的注解有@Component是通用标注,@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问。SpringMVC启动时怎么被自动扫描然后解析并注册到Bean工厂中去(放到DefaultListableBeanFactory中的Map beanDefinitionMap中 以BeanName为key)?我们今天带着这些问题来了解分析这实现的过程,我们在分析之前先了解一下这些注解。
@Controller标注web控制器,@Service标注Service层的服务,@Respository标注DAO层的数据访问。@Component是通用标注,只是定义为一个类为Bean,SpringMVC会把所有添加@Component注解的类作为使用自动扫描注入配置路径下的备选对象。@Controller、@Service\@Respository只是更加的细化,都是被@Component标注,所以我们比较不推荐使用@Component。源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
String value() default &quot;&quot;;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
String value() default &quot;&quot;;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
String value() default &quot;&quot;;
}

都是有标示@Component
我们在配置文件中,标示配置需要扫描哪些包下,也可以配置对某个包下不扫描,代码如下:

1
2
3
4
&lt;context:component-scan base-package=&quot;cn.test&quot;&gt;
&lt;context:exclude-filter type=&quot;regex&quot; expression=&quot;cn.test.*.*.controller&quot;/&gt;
&lt;context:exclude-filter type=&quot;regex&quot; expression=&quot;cn.test.*.*.controller2&quot;/&gt;
&lt;/context:component-scan&gt;

说明:
指定的不扫描包,指定的扫描包
SpringMVC先读取配置文件,然后根据context:component-scan中属性base-package去扫描指定包下的class和jar文件,把标示@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问等注解的都获取,并注册为Bean类放到Bean工厂。

三、ComponentScanBeanDefinitionParser
通过阅读源码,知道接口BeanDefinitionParser可以实现将自定义的标签转化为 BeanDefinition类。而则是由ComponentScanBeanDefinitionParser进行解析的。

展开ComponentScanBeanDefinitionParser中的parse方法。可以看到主要完成以下工作。
(1)获取context:component-scan 配置的属性base-package的值,然后放到数组。
(2)创建扫描对应包下的class和jar文件的对象ClassPathBeanDefinitionScanner ,由这个类来实现扫描包下的class和jar文件并把注解的Bean包装成BeanDefinition。
(3)BeanDefinition注册到Bean工厂。

1
2
3
4
5
6
7
8
9
10
11
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set&lt;BeanDefinitionHolder&gt; beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}

再展开configureScanner方法。首先通过findCandidateComponents获取候选bean definition。然后注册到registry中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected Set&lt;BeanDefinitionHolder&gt; doScan(String... basePackages) {
Assert.notEmpty(basePackages, &quot;At least one base package must be specified&quot;);
Set&lt;BeanDefinitionHolder&gt; beanDefinitions = new LinkedHashSet&lt;BeanDefinitionHolder&gt;();
for (String basePackage : basePackages) {
Set&lt;BeanDefinition&gt; candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

四、筛选类
展开findCandidateComponents方法。首先获取路径下的资源Resource,然后判断资源是否可读,并且获取可读资源的MetadataReader对象,然后再调用isCandidateComponent(MetadataReader)判段是否是候选组件,如果是,则生成该metadataReader的ScannedGenericBeanDefinition对象。最后判断ScannedGenericBeanDefinition是否为候选的,如果是则添加到工厂中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public Set&lt;BeanDefinition&gt; findCandidateComponents(String basePackage) {
Set&lt;BeanDefinition&gt; candidates = new LinkedHashSet&lt;BeanDefinition&gt;();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + &quot;/&quot; + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace(&quot;Scanning &quot; + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug(&quot;Identified candidate component class: &quot; + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug(&quot;Ignored because not a concrete top-level class: &quot; + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace(&quot;Ignored because not matching any filter: &quot; + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
&quot;Failed to read candidate component class: &quot; + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace(&quot;Ignored because not readable: &quot; + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(&quot;I/O failure during classpath scanning&quot;, ex);
}
return candidates;
}

展开isCandidateComponent方法。通过变量excludeFilters, includeFilters去匹配传递进来的MetadataReader,如果与excludeFilter匹配成功返回false, 与includeFilter匹配成功返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}

五、注册bean factory

在上面doScan方法中有

1
2
//注册到工厂中
registerBeanDefinition(definitionHolder, this.registry);

这样一行代码将beanDefinition注册到registry中,这是一个BeanDefinitionRegistry,下面是它的接口定义及继承结构:
1
2
3
4
5
6
7
8
9
10
public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
}

我们可以看到接口中定义了诸多beandefinition的注册,删除,获取等方法,并且Spring为我们提供了三个内部实现,那么运行时,和之前文章的分析一样,使用了DefaultListableBeanFactory。
展开registerBeanDefinition方法。可以看出,所有的beanDefinition都由实例变量beanDefinitionMap来保存管理,他是一个ConcurrentHashMap,beanName作为键,beanDefinition对象作为值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, &quot;Bean name must not be empty&quot;);
Assert.notNull(beanDefinition, &quot;BeanDefinition must not be null&quot;);
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
&quot;Validation of bean definition failed&quot;, ex);
}
}
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
&quot;Cannot register bean definition [&quot; + beanDefinition + &quot;] for bean '&quot; + beanName +
&quot;': There is already [&quot; + oldBeanDefinition + &quot;] bound.&quot;);
}
else if (oldBeanDefinition.getRole() &lt; beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn(&quot;Overriding user-defined bean definition for bean '&quot; + beanName +
&quot;' with a framework-generated bean definition: replacing [&quot; +
oldBeanDefinition + &quot;] with [&quot; + beanDefinition + &quot;]&quot;);
}
}
else {
if (this.logger.isInfoEnabled()) {
this.logger.info(&quot;Overriding bean definition for bean '&quot; + beanName +
&quot;': replacing [&quot; + oldBeanDefinition + &quot;] with [&quot; + beanDefinition + &quot;]&quot;);
}
}
}
else {
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

Jerky Lu wechat
欢迎加入微信公众号